package de.digisalt.dsnesds.editors.projectmeta;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.EventObject;
import java.util.Observable;
import java.util.Observer;

import org.eclipse.core.internal.resources.ResourceException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.command.BasicCommandStack;
import org.eclipse.emf.common.command.CommandStackListener;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.ui.forms.editor.FormEditor;
import org.eclipse.ui.forms.editor.FormPage;

import de.digisalt.dsnesds.core.utils.interfaces.IRefreshableEditorPart;
import de.digisalt.dsnesds.editors.projectmeta.model.Loader;
import de.digisalt.dsnesds.editors.projectmeta.model.Model;
import de.digisalt.dsnesds.editors.projectmeta.model.Serializer;
import de.digisalt.dsnesds.editors.projectmeta.model.WrongDsnesdsVersionException;
import de.digisalt.dsnesds.editors.projectmeta.pages.ProjectMetaFormPage;

/**
 * @author flo The baseclass for DSNESDSs project metadata editor. It is a
 *         Formeditor and could be extended to have multiple pages in the
 *         future.
 */
@SuppressWarnings("restriction")
public class ProjectMetaEditor extends FormEditor
{
	/**
	 * This is the model for the editor
	 */
	private Model		model;

	/**
	 * This boolean indicates that the model has unsaved changes --> enable save
	 */
	private boolean	dirty;

	/**
	 * The IResource read by the file
	 */
	private IFile		input;

	public ProjectMetaEditor()
	{
		commandStack = new BasicCommandStack();

		// Add a listener to set the most recent command's affected objects to be
		// the selection of the viewer with focus.
		//
		commandStack.addCommandStackListener(new CommandStackListener()
		{

			@Override
			public void commandStackChanged(final EventObject event)
			{
				getContainer().getDisplay().asyncExec(new Runnable()
				{

					@Override
					public void run()
					{
						firePropertyChange(IEditorPart.PROP_DIRTY);

						if (getSelectedPage() instanceof IRefreshableEditorPart)
						{
							// refresh the editor pages
							((IRefreshableEditorPart) getSelectedPage()).doRefresh();
						}
					}
				});
			}
		});
	}

	@Override
	protected void addPages()
	{
		// create the model
		try
		{
			createModel();
		}
		catch (Exception e1)
		{
			e1.printStackTrace();
		}

		try
		{

			FormPage page1 = new ProjectMetaFormPage(this,
					"de.digisalt.projectmeta.page1", input.getName(), this.model,
					this.commandStack);
			int index = addPage(page1);
			setPageText(index, "Project Metadata");
		}
		catch (PartInitException e)
		{
			e.printStackTrace();
		}

		// observe the model for changes so we can react on them --> dirty flag,
		// etc.
		ModelObserver modelDirtyObserver = new ModelObserver(this);
		model.addObserver(modelDirtyObserver);
		Thread t = new Thread(model);
		t.start();

		this.setPartName("project.meta");
	}

	/**
	 * Flag the settings as dirty, enable the <b>Save</b> options, and update the
	 * editor's modification indicator (*).
	 * 
	 * @param isDirty
	 *          :boolean
	 */
	protected void setIsDirty(final boolean isDirty)
	{
		firePropertyChange(IEditorPart.PROP_DIRTY);
	}

	/**
	 * This is the method called to load a resource into the editing domain's
	 * resource set based on the editor's input. <!-- begin-user-doc --> <!--
	 * end-user-doc -->
	 * 
	 * @return the model instance read by the loader
	 * @throws Exception
	 * @generated
	 */
	public void createModel() throws Exception
	{
		// get the editor input
		IFileEditorInput editorInput = ((IFileEditorInput) getEditorInput());
		// store it for future reference
		this.input = editorInput.getFile();

		try
		{
			model = Loader.load(input);
		}
		catch (Exception e)
		{
			if (e instanceof WrongDsnesdsVersionException)
			{
				throw e;
			}
			else
			{
				throw new Exception("An error occurred loading the model.");
			}
		}
	}

	/**
	 * This will be checked to decide weather or not to enable save functionality
	 */
	@Override
	public boolean isDirty()
	{
		if (model != null)
		{
			return model.isDirty();
		}
		else
		{
			return false;
		}
	}

	private void setDirty()
	{
		dirty = true;
		firePropertyChange(PROP_DIRTY);
	}

	private void setNotDirty()
	{
		dirty = false;
		// is this needed?
		firePropertyChange(PROP_DIRTY);
	}

	@Override
	public void doSave(final IProgressMonitor monitor)
	{
		// Do the work within an operation because this is a long running activity
		// that modifies the workbench.
		WorkspaceModifyOperation operation = new WorkspaceModifyOperation()
		{
			@Override
			public void execute(final IProgressMonitor monitor)
					throws ResourceException
			{
				try
				{
					Serializer.save(input, model);
				}
				catch (IOException e)
				{
					e.printStackTrace();
				}
				catch (CoreException e)
				{
					e.printStackTrace();
				}
			}
		};

		try
		{
			new ProgressMonitorDialog(getSite().getShell()).run(true, false,
					operation);
		}
		catch (InvocationTargetException e)
		{
			e.printStackTrace();
		}
		catch (InterruptedException e)
		{
			e.printStackTrace();
		}

		if (dirty)
		{
			dirty = false;
		}

	}

	@Override
	public void doSaveAs()
	{
		// don't do anything as the project.meta file needs to be saved at project
		// root with exactly that name
	}

	@Override
	public boolean isSaveAsAllowed()
	{
		// don't allow this saving with another name as this is an integral
		// requirement of DSNESDS parser
		return false;
	}

	class ModelObserver implements Observer
	{
		private final ProjectMetaEditor	editor;

		public ModelObserver(ProjectMetaEditor editor)
		{
			this.editor = editor;
		}

		@Override
		public void update(Observable o, Object arg)
		{
			if (arg instanceof Boolean && o instanceof Model)
			{
				boolean newDirtyState = ((Boolean) arg).booleanValue();
				if (newDirtyState)
				{
					Display.getDefault().syncExec(new Runnable()
					{

						@Override
						public void run()
						{
							editor.setDirty();
						}
					});
				}
				else
				{
					Display.getDefault().asyncExec(new Runnable()
					{

						@Override
						public void run()
						{
							editor.setNotDirty();
						}
					});
				}
			}
		}

	}
}
